/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.jk.common;

import java.io.IOException;

import javax.management.Attribute;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;

import org.apache.jk.core.JkHandler;

/**
 * Load the HTTP or RMI adapters for MX4J and JMXRI.
 * 
 * Add "mx.enabled=true" in jk2.properties to enable it. You could also select
 * http and/or jrmp protocol, with mx.httpPort, mx.httpHost, mxjrmpPort and
 * mx.jrmpPort.
 * <p />
 * If you run into an error message like "SystemId Unknown; Line #12; Column
 * #81; Cannot add attribute name after child nodes or before an element is
 * produced. Attribute will be ignored." after setting mx.enabled to true, you
 * probably need a newer version of Xalan. See the RELEASE-NOTES document
 * section on XML Parsers for more information.
 * 
 */
public class JkMX extends JkHandler {
	MBeanServer mserver;
	private boolean enabled = false;
	private boolean log4jEnabled = true;
	private int httpport = -1;
	private String httphost = "localhost";
	private String authmode = "none";
	private String authuser = null;
	private String authpassword = null;
	private int jrmpport = -1;
	private String jrmphost = "localhost";
	private boolean useXSLTProcessor = true;

	public JkMX() {
	}

	/* -------------------- Public methods -------------------- */

	/**
	 * Enable the MX4J adapters (new way)
	 */
	public void setEnabled(boolean b) {
		enabled = b;
	}

	public boolean getEnabled() {
		return enabled;
	}

	/**
	 * Enable the Log4j MBean)
	 */
	public void setLog4jEnabled(boolean b) {
		log4jEnabled = b;
	}

	public boolean getLog4jEnabled() {
		return log4jEnabled;
	}

	/**
	 * Enable the MX4J adapters (old way, compatible)
	 */
	public void setPort(int i) {
		enabled = (i != -1);
	}

	public int getPort() {
		return ((httpport != -1) ? httpport : jrmpport);
	}

	/**
	 * Enable the MX4J HTTP internal adapter
	 */
	public void setHttpPort(int i) {
		httpport = i;
	}

	public int getHttpPort() {
		return httpport;
	}

	public void setHttpHost(String host) {
		this.httphost = host;
	}

	public String getHttpHost() {
		return httphost;
	}

	public void setAuthMode(String mode) {
		authmode = mode;
	}

	public String getAuthMode() {
		return authmode;
	}

	public void setAuthUser(String user) {
		authuser = user;
	}

	public String getAuthUser() {
		return authuser;
	}

	public void setAuthPassword(String password) {
		authpassword = password;
	}

	public String getAuthPassword() {
		return authpassword;
	}

	/**
	 * Enable the MX4J JRMP internal adapter
	 */
	public void setJrmpPort(int i) {
		jrmpport = i;
	}

	public int getJrmpPort() {
		return jrmpport;
	}

	public void setJrmpHost(String host) {
		this.jrmphost = host;
	}

	public String getJrmpHost() {
		return jrmphost;
	}

	public boolean getUseXSLTProcessor() {
		return useXSLTProcessor;
	}

	public void setUseXSLTProcessor(boolean uxsltp) {
		useXSLTProcessor = uxsltp;
	}

	/* ==================== Start/stop ==================== */
	ObjectName httpServerName = null;
	ObjectName jrmpServerName = null;

	/**
	 * Initialize the worker. After this call the worker will be ready to accept
	 * new requests.
	 */
	public void loadAdapter() throws IOException {
		boolean httpAdapterLoaded = false;
		boolean jrmpAdapterLoaded = false;

		if ((httpport != -1) && classExists("mx4j.adaptor.http.HttpAdaptor")) {
			try {
				httpServerName = registerObject(
						"mx4j.adaptor.http.HttpAdaptor",
						"Http:name=HttpAdaptor");

				if (httphost != null)
					mserver.setAttribute(httpServerName, new Attribute("Host",
							httphost));
				mserver.setAttribute(httpServerName, new Attribute("Port",
						new Integer(httpport)));

				if ("none".equals(authmode) || "basic".equals(authmode)
						|| "digest".equals(authmode))
					mserver.setAttribute(httpServerName, new Attribute(
							"AuthenticationMethod", authmode));

				if (authuser != null && authpassword != null)
					mserver.invoke(httpServerName, "addAuthorization",
							new Object[] { authuser, authpassword },
							new String[] { "java.lang.String",
									"java.lang.String" });

				if (useXSLTProcessor) {
					ObjectName processorName = registerObject(
							"mx4j.adaptor.http.XSLTProcessor",
							"Http:name=XSLTProcessor");
					mserver.setAttribute(httpServerName, new Attribute(
							"ProcessorName", processorName));
				}

				// starts the server
				mserver.invoke(httpServerName, "start", null, null);

				log.info("Started MX4J console on host " + httphost
						+ " at port " + httpport);

				httpAdapterLoaded = true;

			} catch (Throwable t) {
				httpServerName = null;
				log.error("Can't load the MX4J http adapter ", t);
			}
		}

		if ((httpport != -1) && (!httpAdapterLoaded)
				&& classExists("mx4j.tools.adaptor.http.HttpAdaptor")) {
			try {
				httpServerName = registerObject(
						"mx4j.tools.adaptor.http.HttpAdaptor",
						"Http:name=HttpAdaptor");

				if (httphost != null)
					mserver.setAttribute(httpServerName, new Attribute("Host",
							httphost));
				mserver.setAttribute(httpServerName, new Attribute("Port",
						new Integer(httpport)));

				if ("none".equals(authmode) || "basic".equals(authmode)
						|| "digest".equals(authmode))
					mserver.setAttribute(httpServerName, new Attribute(
							"AuthenticationMethod", authmode));

				if (authuser != null && authpassword != null)
					mserver.invoke(httpServerName, "addAuthorization",
							new Object[] { authuser, authpassword },
							new String[] { "java.lang.String",
									"java.lang.String" });

				if (useXSLTProcessor) {
					ObjectName processorName = registerObject(
							"mx4j.tools.adaptor.http.XSLTProcessor",
							"Http:name=XSLTProcessor");
					mserver.setAttribute(httpServerName, new Attribute(
							"ProcessorName", processorName));
				}
				// starts the server
				mserver.invoke(httpServerName, "start", null, null);
				if (log.isInfoEnabled())
					log.info("Started MX4J console on host " + httphost
							+ " at port " + httpport);

				httpAdapterLoaded = true;

			} catch (Throwable t) {
				httpServerName = null;
				log.error("Can't load the MX4J http adapter ", t);
			}
		}

		if ((jrmpport != -1) && classExists("mx4j.tools.naming.NamingService")) {
			try {
				jrmpServerName = registerObject(
						"mx4j.tools.naming.NamingService",
						"Naming:name=rmiregistry");
				mserver.setAttribute(jrmpServerName, new Attribute("Port",
						new Integer(jrmpport)));
				mserver.invoke(jrmpServerName, "start", null, null);
				if (log.isInfoEnabled())
					log.info("Creating " + jrmpServerName);

				// Create the JRMP adaptor
				ObjectName adaptor = registerObject(
						"mx4j.adaptor.rmi.jrmp.JRMPAdaptor",
						"Adaptor:protocol=jrmp");

				mserver
						.setAttribute(adaptor,
								new Attribute("JNDIName", "jrmp"));

				mserver
						.invoke(
								adaptor,
								"putNamingProperty",
								new Object[] {
										javax.naming.Context.INITIAL_CONTEXT_FACTORY,
										"com.sun.jndi.rmi.registry.RegistryContextFactory" },
								new String[] { "java.lang.Object",
										"java.lang.Object" });

				String jrpmurl = "rmi://" + jrmphost + ":"
						+ Integer.toString(jrmpport);

				mserver
						.invoke(adaptor, "putNamingProperty", new Object[] {
								javax.naming.Context.PROVIDER_URL, jrpmurl },
								new String[] { "java.lang.Object",
										"java.lang.Object" });

				// Registers the JRMP adaptor in JNDI and starts it
				mserver.invoke(adaptor, "start", null, null);
				if (log.isInfoEnabled())
					log.info("Creating " + adaptor + " on host " + jrmphost
							+ " at port " + jrmpport);

				jrmpAdapterLoaded = true;

			} catch (Exception ex) {
				jrmpServerName = null;
				log.error("MX4j RMI adapter not loaded: " + ex.toString());
			}
		}

		if ((httpport != -1) && (!httpAdapterLoaded)
				&& classExists("com.sun.jdmk.comm.HtmlAdaptorServer")) {
			try {
				httpServerName = registerObject(
						"com.sun.jdmk.comm.HtmlAdaptorServer",
						"Adaptor:name=html,port=" + httpport);
				if (log.isInfoEnabled())
					log.info("Registering the JMX_RI html adapter "
							+ httpServerName + " at port " + httpport);

				mserver.setAttribute(httpServerName, new Attribute("Port",
						new Integer(httpport)));

				mserver.invoke(httpServerName, "start", null, null);

				httpAdapterLoaded = true;
			} catch (Throwable t) {
				httpServerName = null;
				log.error("Can't load the JMX_RI http adapter " + t.toString());
			}
		}

		if ((!httpAdapterLoaded) && (!jrmpAdapterLoaded))
			log.warn("No adaptors were loaded but mx.enabled was defined.");

	}

	public void destroy() {
		try {
			if (log.isInfoEnabled())
				log.info("Stoping JMX ");

			if (httpServerName != null) {
				mserver.invoke(httpServerName, "stop", null, null);
			}
			if (jrmpServerName != null) {
				mserver.invoke(jrmpServerName, "stop", null, null);
			}
		} catch (Throwable t) {
			log.error("Destroy error" + t);
		}
	}

	public void init() throws IOException {
		try {
			mserver = getMBeanServer();

			if (enabled) {
				loadAdapter();
			}
			if (log4jEnabled) {
				try {
					registerObject(
							"org.apache.log4j.jmx.HierarchyDynamicMBean",
							"log4j:hierarchy=default");
					if (log.isInfoEnabled())
						log.info("Registering the JMX hierarchy for Log4J ");
				} catch (Throwable t) {
					if (log.isInfoEnabled())
						log.info("Can't enable log4j mx: ", t);
				}
			}
		} catch (Throwable t) {
			log.error("Init error", t);
		}
	}

	public void addHandlerCallback(JkHandler w) {
	}

	MBeanServer getMBeanServer() {
		MBeanServer server;
		if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
			server = (MBeanServer) MBeanServerFactory.findMBeanServer(null)
					.get(0);
		} else {
			server = MBeanServerFactory.createMBeanServer();
		}
		return (server);
	}

	private static boolean classExists(String className) {
		try {
			Thread.currentThread().getContextClassLoader().loadClass(className);
			return true;
		} catch (Throwable e) {
			if (log.isInfoEnabled())
				log.info("className [" + className + "] does not exist");
			return false;
		}
	}

	private ObjectName registerObject(String className, String oName)
			throws Exception {
		Class c = Class.forName(className);
		Object o = c.newInstance();
		ObjectName objN = new ObjectName(oName);
		mserver.registerMBean(o, objN);
		return objN;
	}

	private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
			.getLog(JkMX.class);

}
